package org.timeticker.impl;

import org.apache.log4j.Logger;
import org.timeticker.*;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Activity list.<br/>
 * <i><strong>Note :</strong> Implementation is thread safe</i>
 *
 * @author Ivan Latysh
 * @version 0.1
 * @since 16-Jul-2007 : 7:37:26 PM
 */
public class ActivityListImpl extends AbstractObjectList<Activity> implements ActivityList {
  // Error prefix ACL00001

  /** logger */
  protected Logger logger = Logger.getLogger(this.getClass().getName());
  /** Daily activities */
  public final Map<Day, DailyActivityList> dailyActivities = new HashMap<Day, DailyActivityList>();
  /** Activity listeners */
  protected final List<ActivityListener> activityListeners = new ArrayList<ActivityListener>();
  /** Date range this activity list hold at the moment */
  protected DayRange range;
  /** Day compare format */ 
  private SimpleDateFormat compateFormat = new SimpleDateFormat("yyyyDD");

  /**
   * Return date range
   *
   * @return date range
   */
  public DayRange getDateRange() {
    return range;
  }

  /**
   * Set date range
   *
   * @param range date range
   */
  public void setDateRange(DayRange range) {
    this.range = range;
  }

  /** {@inheritDoc} */
  public DailyActivityList getDailyActivity(Day day) {
    return dailyActivities.get(day);
  }

  /** {@inheritDoc} */
  public void addDailyActivity(DailyActivityList list) {
    synchronized(this) {
      // add list to daily activities list
      dailyActivities.put(list.getDay(), list);
      // add all activities to this list
      for(int i=0; i<list.size(); i++) super.add(list.get(i));
      // run time integrity check
      timeIntegrityCheck();
      // fire list changed
      fireListChanged();
    }
  }

  /**
   * Update modified flag
   *
   * @param modified modified flag
   */
  public void setModified(boolean modified) {
    this.modified = modified;
  }

  /** {@inheritDoc} */
  public final Activity getActivityInProgress() {
    // look for in-progress activity
    for(int i=size()-1; i>=0; i--){
      Activity _activity = get(i);
      if (null== _activity.getFinishTime()) return _activity;
    }
    return null;
  }

  /** {@inheritDoc} */
  public boolean add(Activity activity) {
    boolean added = false;
    synchronized(this) {
      // check activityDay
      if (null==activity.getDay()) activity.setDay(range.getTo());
      // add activity to general list
      added = super.add(activity);
      // add activity to daily activity list
      if (!dailyActivities.containsKey(activity.getDay())) {
        DailyActivityList da = new DailyActivityListImpl();
        da.setDay(activity.getDay());
        dailyActivities.put(da.getDay(), da);
      }
      dailyActivities.get(activity.getDay()).add(activity);
      // run time integrity check
      if (added) timeIntegrityCheck();
      // finalize past activities
      if (new Day().compareTo(activity.getDay()) > 0) {
        if (null==activity.getFinishTime()) activity.setFinishTime(new Date(System.currentTimeMillis()));
        logger.debug("Past activity could not be started.");
        // TODO display user the message that he can't resume activity in the past
      }
    }
    // update modified flag
    setModified(true);
    // fire an event
    fireActivityAdded(activity);
    // debug info
    if (logger.isDebugEnabled()) logger.debug("Activity added {activity="+activity+"}.");
    // return result
    return added;
  }

  /** {@inheritDoc} */
  public boolean remove(Activity activity) {
    boolean removed = false;
    synchronized(this) {
      // remove from general list
      removed = super.remove(activity);
      // remove from daily activity list
      if (dailyActivities.containsKey(activity.getDay())) {
        removed = dailyActivities.get(activity.getDay()).remove(activity);
      }
    }

    if (removed) {
      // update modified flag
      setModified(true);
      // fire activity removed event
      fireActivityRemoved(activity);
    }
    // return removal result
    return removed;
  }

  /** {@inheritDoc} */
  public int size() {
    synchronized(this) {
      return super.size();
    }
  }

  /** {@inheritDoc} */
  public Activity get(int index) {
    synchronized(this) {
      return super.get(index);
    }
  }

  /**
   * Always return null
   *
   * @param key object key
   * @return object
   */
  public Activity get(String key) {
    return null;
  }

  /** {@inheritDoc} */
  public void start(Activity activity) throws IllegalArgumentException {
    synchronized(this) {
      if (!contains(activity)) throw new IllegalArgumentException("Activity {"+activity+"} is not in my list.");
      // stop in progress activity
      Activity inProgress = getActivityInProgress();
      if (null!=inProgress && inProgress!=activity) inProgress.stop();

      // compare dated to update finish time on old lists
      if (new Day().compareTo(activity.getDay()) > 0) {
        if (null==activity.getFinishTime()) activity.setFinishTime(new Date(System.currentTimeMillis()));
        logger.debug("Past activity could not be started.");
        // TODO display user the message that he can't resume activity in the past
      }  else {
        // start new activity
        activity.start();
      }
    }
    // update modified flag
    setModified(true);
    // fire object modified exception
    fireObjectChanged(indexOf(activity), activity);
    // fire activity started
    fireActivityStarted(activity);
    // debug info
    if (logger.isDebugEnabled()) {
      if (null==activity.getFinishTime()) {
        logger.debug("Activity started {activity="+activity+"}.");
      } else {
        logger.debug("Activity could not be started {activity="+activity+"}.");
      }
    }
  }

  /** {@inheritDoc} */
  public void stop(Activity activity) throws IllegalArgumentException {
    synchronized(this) {
      if (!contains(activity)) throw new IllegalArgumentException("Activity {"+activity+"} is not in my list.");
      // stop activity
      activity.stop();
    }
    // update modified flag
    modified = true;
    // fire object modified exception
    fireObjectChanged(indexOf(activity), activity);
    // fire activity stopped event
    fireActivityStopped(activity);
    // debug info
    logger.debug("Activity {"+activity+"} has been stopped.");
  }

  /** {@inheritDoc} */
  public void addActivityListener(ActivityListener l) {
    activityListeners.add(l);
  }

  /** {@inheritDoc} */
  public void removeActivityListener(ActivityListener l) {
    activityListeners.remove(l);
  }

  /**
   * Fire activity added event
   *
   * @param activity added activity
   */
  protected void fireActivityAdded(Activity activity) {
    for (ActivityListener l: activityListeners) {
      l.activityAdded(activity);
    }
  }

  /**
   * Fire activity removed event
   *
   * @param activity removed activity
   */
  protected void fireActivityRemoved(Activity activity) {
    for (ActivityListener l: activityListeners) {
      l.activityRemoved(activity);
    }
  }

  /**
   * Fire activity started event
   *
   * @param activity started activity
   */
  protected void fireActivityStarted(Activity activity) {
    for (ActivityListener l: activityListeners) {
      l.activityStarted(activity);
    }
  }

  /**
   * Fire activity stopped event
   *
   * @param activity stopped activity
   */
  protected void fireActivityStopped(Activity activity) {
    for (ActivityListener l: activityListeners) {
      l.activityStopped(activity);
    }
  }

  /** {@inheritDoc} */
  protected void fireObjectAdded(int index, Activity object) {
    super.fireObjectAdded(index, object);
    fireActivityAdded(object);
  }

  /** {@inheritDoc} */
  protected void fireObjectRemoved(int index, Activity object) {
    super.fireObjectRemoved(index, object);
    fireActivityRemoved(object);
  }

  /**
   * Sanity chech method make sure that time is OK
   */
  private void timeIntegrityCheck() {
    synchronized(this) {
      // run open close checks
      for(int i=0; i<size()-1; i++) {
        Activity act = get(i);
        // make sure start time is set for all activities
        if (null==act.getStartTime()) {
          act.setStartTime(i>0 ?get(i-1).getFinishTime() : range.getFrom());
          // update modified flag
          modified = true;
        }
        // make sure that finish time is set for all activities except the last one
        if (null==act.getFinishTime() && i!=size()-1) {
          act.setFinishTime(i<size()-2 ?get(i+1).getStartTime() :new Date(System.currentTimeMillis()));
          // update modified flag
          modified = true;
        }
      }
      // run time continuity check
      for(int i=0; i<size()-1; i++) {
        Activity act = get(i);
        Activity actn = get(i+1);
        // make sure time are continues
        if (act.getFinishTime() != actn.getStartTime()) {
          actn.setStartTime(act.getFinishTime());
          // update modified flag
          modified = true;
        }
      }
      // update in progress activity
//      if (null==get(size()-1).getFinishTime()) inProgress = get(size()-1);
    }
  }

}
